// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

#include <memory>

#include "cangjie/AST/Walker.h"

#include "Util/Io.h"
#include "Util/FileSystem.h"

#include "CangjieTools/Cjpm.h"

#include "Migrators/ProjectMigrator.h"

using namespace CangjieMigrator;
using namespace Cangjie::AST;

ProjectMigrator::ProjectMigrator(
    Path cangjieHome, Option option, std::string version, std::unique_ptr<Rules::RuleList> rules)
    : Migrator(option, std::move(rules)),
    cangjieHome(std::move(cangjieHome)),
    source(option.source), target(option.target),
    importPath(option.importPath), project(option.source)
{}

bool ProjectMigrator::ConvertPackage(UpdateInstance::Package &package) const
{
    auto compiler = CangjieTools::Compiler(cangjieHome);

    // 1. 编译源文件的语义信息
    compiler.CompileSemaPackage(package);

    // 2. 遍历语法树，并根据更新规则更新
    const auto rootNodes = compiler.GetAstRootNodes();
    for (auto &rootNode : rootNodes) {
        if (!rootNode) {
            std::cerr << "File migrator encountered an AST root node of package " << package.GetPath()
                      << " is a nullptr, skipping traversal. " << std::endl;
            continue;
        }
        if (!rootNode->curFile || rootNode->curFile->fileName.empty()) {
            std::cerr << "Found an AST node of package " << package.GetPath()
                      << " does not belong to any file, skipping traversal." << std::endl;
            continue;
        }
        auto fileName = rootNode->curFile->fileName;
        const auto filePath = package.GetPath() / fileName;
        if (!exists(filePath)) {
            std::cerr << "The file " << filePath << " belonging to an AST node was not found, skipping traversal."
                      << std::endl;
            continue;
        }
        auto &file = package.GetFile(fileName);
        CangjieTools::Compiler::TraverseAst(rootNode, rules, file);
    }

    // 3. 修改ImportInfo
    package.ChangeImport();

    // 4. 将上一步完成的UpdateList根据修改的Position写入target
    if (!package.Generate(target / Util::LexicalRelative(package.GetPath(), source))) {
        std::cerr << "Project migrator generate " << package.GetPath() << " failed. " << std::endl;
        return false;
    }

    return true;
}

bool ProjectMigrator::Convert()
{
    const auto targetDirectory = Platform::TempDirectory();
    const auto cjpm = CangjieTools::Cjpm(cangjieHome);
    auto infos = cjpm.Build(source, targetDirectory.GetPath());
    if (infos.empty()) {
        std::cerr << "Cjpm build failed, please fix it and try again." << std::endl;
        return false;
    }
    std::cout << "Cjpm build end." << std::endl;

    infos.erase(std::remove_if(infos.begin(), infos.end(), [this](const PackageCompileInfo &info) {
            return !Util::IsSubDir(source, info.packagePath);
        }), infos.end());

    project.SetPackages(infos);

    bool success = true;
    const auto &packages = project.GetPackages();
    for (size_t i = 0; i < packages.size(); ++i) {
        const auto &package = packages[i];
        const auto &packagePath = package->GetPath();
        std::cout << "Convert package " <<
            "[" << (i + 1) << "/" << packages.size() << "] " << package->GetPath() << std::endl;
        if (!Util::IsSubDir(source, packagePath)) {
            std::cout << "Package " << packagePath << " is not belongs to project, jump update." << std::endl;
            continue;
        }
        if (!ConvertPackage(*package)) {
            std::cerr << "Package " << packagePath << " convert failed." << std::endl;
            success = false;
        }
        std::cout << "Package " << packagePath << " convert success." << std::endl;
    }

    if (project.NeedImportStdx()) {
        std::cout << "Some std packages used in the project have been moved to stdx. Please download and add a "
                     "reference to stdx."
                  << std::endl;
    }

    return success;
}
